Skip to content

E听说答案工具代码解释:

为了帮助大家更好理解我写的史山代码,所以这篇文章诞生了...

同样的,也代表着我不再更新此工具,欢迎大家提交改进版给我😘

Windwos端:

使用Python实现,主要代码主要包含四个函数:get_result(), A(), B()update_folder_list(),以下是分段解释

get_result(self, folder_name)

这个函数的主要目的是获取模考试题的结果

它首先清空文本区域,然后计算指定文件夹中以'content0001000'开头的文件数量。根据文件数量,它会调用A()B()函数来获取标题。如果文件数量不是3或7,它会显示一条消息,提示这可能不是正常的模考试题,并隐藏文件列表

python
            self.text_area.clear() #清空文本区域
        file_count = len([name for name in os.listdir(os.path.join(self.base_path, folder_name)) if
                          name.startswith('content0001000')]) 
        if file_count == 3:
            titles = self.A()
        elif file_count == 7:
            titles = self.B() #计算文件数量判断是高中还是初中~
        else:
            self.text_area.setText("这似乎不是正常模考试题,请重新选择吧")
            self.text_area.setAlignment(Qt.AlignCenter)
            self.text_area.show()
            self.folder_list.hide()
            return

然后,它会遍历标题列表,对于每个标题,它会打开对应的'content.json'文件,并尝试读取其中的数据。如果数据中包含'question'字段,它会遍历问题列表,并对每个问题,提取其"value"的值作为答案,然后显示它。如果用户选择了打印关键词,并且问题中包含'keywords'字段,它还会显示关键词。如果用户选择了只显示一个答案,它会在显示一个答案后停止

python
            error_count = 0
        for i in range(2, len(titles) + 2):
            file_name = f'content0001000{i}'
            file_path = os.path.join(self.base_path, folder_name, file_name, 'content.json')
            self.text_area.append(f'{titles[i - 2]}:\n')
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                if 'question' in data['info']:
                    questions = data['info']['question']
                    for j, question in enumerate(questions):
                        self.text_area.append(f'角色扮演 {j + 1} :\n')
                        for k, answer in enumerate(question['std']):
                            answer_value = answer["value"].replace('</p><p>', ' ')
                            self.text_area.append(f'{k + 1}. {answer_value}\n')
                            if self.print_keywords.isChecked() and 'keywords' in question:
                                keywords = question["keywords"].replace('|', ', ')
                                self.text_area.append(f'关键词: {keywords}\n')
                            if self.only_one_answer.isChecked():
                                break
                else:
                    self.text_area.append('\n')
                    for k, answer in enumerate(data['info']['std']):
                        answer_value = answer["value"].replace('</p><p>', ' ')
                        self.text_area.append(f'{k + 1}. {answer_value}\n')
                        if self.only_one_answer.isChecked():
                            break
                self.text_area.append('')

如果数据中不包含'question'字段,它会直接显示答案。如果在读取文件或处理数据时发生错误,它会显示一条获取错误的消息,并增加错误计数。如果所有的标题都发生了错误,它会显示一条消息,提示这可能不是正常的模考试题,并居中对齐文本

python
         except Exception as e:
                self.text_area.append("获取错误")
                error_count += 1
        if error_count == len(titles):
            self.text_area.setText("这似乎不是正常模考试题,请重新选择吧")
            self.text_area.setAlignment(Qt.AlignCenter)

最后,它会将文本光标移动到开始位置,并显示文本区域,隐藏文件列表

python
        self.text_area.moveCursor(QTextCursor.Start)
        self.text_area.show()
        self.folder_list.hide()

A(self)

这个函数返回一个包含'角色扮演'和'故事复述'两个元素的列表,实际上就是用来告诉界面按初中还是高中的格式显示

B(self)

这个函数返回一个包含'听选信息1','听选信息2','听选信息3','回答问题','短文复述'和'提问'六个元素的列表。作用同上

update_folder_list(self)

这个函数会更新文件列表。它首先获取基础路径下所有的文件夹,并按照创建时间从新到旧排序。然后,它会清空文件列表,并将排序后的文件夹添加到文件列表中

python
        def update_folder_list(self):
        folders = [(name, os.path.getmtime(os.path.join(self.base_path, name))) for name in os.listdir(self.base_path)
                   if name.isdigit()]
        folders.sort(key=lambda x: x[1], reverse=True)  # 按创建时间从新到旧排序
        self.folder_list.clear()
        for folder, mtime in folders:
            mtime_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(mtime))
            item = QListWidgetItem(f'{folder.ljust(20)} {mtime_str.rjust(20)}')
            item.setTextAlignment(Qt.AlignLeft)
            self.folder_list.addItem(item)

包含界面的完整代码

python

import sys
import os
import json
import time
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTextEdit, QLabel, QCheckBox, \
    QListWidget, QListWidgetItem, QMessageBox
from PyQt5.QtGui import QFontDatabase, QFont, QTextCursor, QIcon, QMovie
from PyQt5.QtCore import Qt, QThread, pyqtSignal


class ScanThread(QThread):
    finished = pyqtSignal()

    def __init__(self, window):
        super().__init__()
        self.window = window

    def run(self):
        self.window.update_folder_list()
        self.finished.emit()  # 发送完成信号

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("AT-ETS")
        self.resize(1024, 620)
        self.setWindowOpacity(0.9)
        self.setWindowIcon(QIcon('icon.ico'))

        self.layout = QVBoxLayout(self)

        # 创建一个新的水平布局,用于包含标题栏和加载动画
        self.top_layout = QHBoxLayout()
        self.layout.addLayout(self.top_layout)

        self.title_bar = QWidget(self)
        self.title_layout = QHBoxLayout(self.title_bar)
        self.top_layout.addWidget(self.title_bar)

        # 加载字体文件
        self.font_id = QFontDatabase.addApplicationFont('Fonts/MiSans-Light.ttf')
        self.font_families = QFontDatabase.applicationFontFamilies(self.font_id)
        self.font_family = self.font_families[0] if self.font_families else "Helvetica"



        self.home_button = QPushButton("主页", self.title_bar)
        self.home_button.setFont(QFont(self.font_family, 12))
        self.title_layout.addWidget(self.home_button)

        self.only_one_answer = QCheckBox("只显示一个答案", self.title_bar)
        self.only_one_answer.setFont(QFont(self.font_family, 12))
        self.title_layout.addWidget(self.only_one_answer)

        self.print_keywords = QCheckBox("显示关键词", self.title_bar)
        self.print_keywords.setFont(QFont(self.font_family, 12))
        self.title_layout.addWidget(self.print_keywords)
        self.title_layout.addStretch()

        self.text_area = QTextEdit(self)
        self.text_area.setFont(QFont(self.font_family, 14))
        self.layout.addWidget(self.text_area)

        self.folder_list = QListWidget(self)
        self.folder_list.setFont(QFont(self.font_family, 14))
        self.folder_list.setFrameShape(QListWidget.NoFrame)
        self.layout.addWidget(self.folder_list)

        self.appdata_path = os.getenv('APPDATA')
        self.base_path = os.path.join(self.appdata_path, '74656D705F74656D705F74656D705F74002')

        #加载GIF动画
        self.loading_movie = QMovie("loading.gif")
        self.loading_label = QLabel(self)
        self.loading_label.setMovie(self.loading_movie)
        self.loading_label.hide()
        self.loading_label.setAlignment(Qt.AlignCenter)
        self.top_layout.addWidget(self.loading_label)


        self.scan_thread = ScanThread(self)
        self.scan_thread.finished.connect(self.on_scan_finished)

        self.folder_list.itemClicked.connect(self.on_folder_clicked)
        self.home_button.clicked.connect(self.on_home_clicked)

        self.show()
        self.on_home_clicked()



    def on_folder_clicked(self, item):
        folder_name = item.text().split(' ')[0]  # 从列表项中获取文件夹名
        self.get_result(folder_name)

    def on_home_clicked(self):
        self.title_bar.hide()
        self.text_area.hide()
        self.folder_list.hide()
        self.loading_label.show()
        self.loading_movie.start()
        self.scan_thread.start()

    def on_scan_finished(self):
        self.loading_movie.stop()
        self.loading_label.hide()
        self.title_bar.show()
        self.folder_list.show()

    def get_result(self, folder_name):
        self.text_area.clear()
        file_count = len([name for name in os.listdir(os.path.join(self.base_path, folder_name)) if
                          name.startswith('content0001000')])
        if file_count == 3:
            titles = self.A()
        elif file_count == 7:
            titles = self.B()
        else:
            self.text_area.setText("这似乎不是正常模考试题,请重新选择吧")
            self.text_area.setAlignment(Qt.AlignCenter)
            self.text_area.show()
            self.folder_list.hide()
            return
        error_count = 0
        for i in range(2, len(titles) + 2):
            file_name = f'content0001000{i}'
            file_path = os.path.join(self.base_path, folder_name, file_name, 'content.json')
            self.text_area.append(f'{titles[i - 2]}:\n')
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                if 'question' in data['info']:
                    questions = data['info']['question']
                    for j, question in enumerate(questions):
                        self.text_area.append(f'角色扮演 {j + 1} :\n')
                        for k, answer in enumerate(question['std']):
                            answer_value = answer["value"].replace('</p><p>', ' ')
                            self.text_area.append(f'{k + 1}. {answer_value}\n')
                            if self.print_keywords.isChecked() and 'keywords' in question:
                                keywords = question["keywords"].replace('|', ', ')
                                self.text_area.append(f'关键词: {keywords}\n')
                            if self.only_one_answer.isChecked():
                                break
                else:
                    self.text_area.append('\n')
                    for k, answer in enumerate(data['info']['std']):
                        answer_value = answer["value"].replace('</p><p>', ' ')
                        self.text_area.append(f'{k + 1}. {answer_value}\n')
                        if self.only_one_answer.isChecked():
                            break
                self.text_area.append('')
            except Exception as e:
                self.text_area.append("获取错误")
                error_count += 1
        if error_count == len(titles):
            self.text_area.setText("这似乎不是正常模考试题,请重新选择吧")
            self.text_area.setAlignment(Qt.AlignCenter)
        self.text_area.moveCursor(QTextCursor.Start)
        self.text_area.show()
        self.folder_list.hide()


    def A(self):
        return ['角色扮演', '故事复述']

    def B(self):
        return ['听选信息1', '听选信息2', '听选信息3', '回答问题', '短文复述', '提问']

    def update_folder_list(self):
        folders = [(name, os.path.getmtime(os.path.join(self.base_path, name))) for name in os.listdir(self.base_path)
                   if name.isdigit()]
        folders.sort(key=lambda x: x[1], reverse=True)  # 按创建时间从新到旧排序
        self.folder_list.clear()
        for folder, mtime in folders:
            mtime_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(mtime))
            item = QListWidgetItem(f'{folder.ljust(20)} {mtime_str.rjust(20)}')
            item.setTextAlignment(Qt.AlignLeft)
            self.folder_list.addItem(item)




app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())

Android端

介绍

使用Kotlin编写,基于一个在Android14前都未修复的特性来获取答案

class FirstRunCheck(context: Context)

这个类用于检查应用是否是首次运行,以及是否处于单答案模式。它使用SharedPreferences来存储这些信息。

Kotlin

class FirstRunCheck(context: Context) {
    private val sharedPreferences: SharedPreferences = context.getSharedPreferences("MyApp", Context.MODE_PRIVATE)

    fun isFirstRun(): Boolean {
        return sharedPreferences.getBoolean("isFirstRun", true)
    }

    fun setFirstRun() {
        sharedPreferences.edit().putBoolean("isFirstRun", false).apply()
    }

    fun isSingleAnswerMode(): Boolean {
        return sharedPreferences.getBoolean("isSingleAnswerMode", false)
    }

    fun setSingleAnswerMode(isSingleAnswerMode: Boolean) {
        sharedPreferences.edit().putBoolean("isSingleAnswerMode", isSingleAnswerMode).apply()
    }
}

class MainActivity : AppCompatActivity()

这个类是应用的主活动。它包含了一些私有变量,一个判断颜色是否为亮色的函数,以及一个onCreate()函数。

Kotlin

class MainActivity : AppCompatActivity() {
    private val REQUEST_CODE = 0
    private lateinit var job: Job
    var directoryUri =
        Uri.parse("content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata%2Fcom.ets100.secondary%2Ffiles%2FDownload%2FETS_SECONDARY%2Fresource")

    // 判断颜色是否为亮色
    private fun isLightColor(color: Int): Boolean {
        val red = Color.red(color)
        val green = Color.green(color)
        val blue = Color.blue(color)
        val brightness = (red * 299 + green * 587 + blue * 114) / 1000
        return brightness >= 128
    }

在onCreate()函数中,它首先设置了活动的布局。然后,根据当前是否开启了深色模式,设置状态栏的颜色和字体颜色。接着,它检查应用是否是首次运行,如果是,它会显示一条欢迎消息,并请求必要的权限。最后,它设置应用为非首次运行。

Kotlin
        override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // 检查是否开启了深色模式
            val nightModeFlags = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
            if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
                // 如果开启了深色模式,设置状态栏颜色为黑色,字体颜色为白色
                window.statusBarColor = Color.BLACK
                window.decorView.systemUiVisibility = 0
            } else {
                // 如果没有开启深色模式,设置状态栏颜色为白色,字体颜色为黑色
                window.statusBarColor = Color.WHITE
                window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
            }
        }

        val firstRunCheck = FirstRunCheck(this)
        if (firstRunCheck.isFirstRun()) {
            // 这是首次运行,显示消息
            Toast.makeText(this, "欢迎使用,请点击下方授权!", Toast.LENGTH_LONG).show()

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                if (!Environment.isExternalStorageManager()) {
                    val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
                    val uri = Uri.fromParts("package", packageName, null)
                    intent.data = uri
                    startActivity(intent)
                } else {
                    // 已经获得权限,可以执行访问数据目录和所有文件的操作
                }
            } else {
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
                } else {
                    // 已经获得权限,可以执行访问数据目录和所有文件的操作
                }
            }

            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
            intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, directoryUri)
            startActivityForResult(intent, REQUEST_CODE)

            // 设置为非首次运行
            firstRunCheck.setFirstRun()
        }

接下来,我们的这部分代码从SharedPreferences中读取"directory_uri"的值,并将其解析为Uri对象,然后启动协程来获取版本信息 这部分代码在IO线程中启动一个协程,用于从指定的URL获取版本信息。然后,它在主线程中更新UI,显示版本信息

Kotlin
    // 读取URI
val sharedPreferences = getSharedPreferences("app_prefs", MODE_PRIVATE)
val uriString = sharedPreferences.getString("directory_uri", null)
if (uriString != null) {
    directoryUri = Uri.parse(uriString)
}

// 启动协程来获取版本信息
GlobalScope.launch(Dispatchers.IO) {
    try {
        title.text = "正在检查更新"
        val doc = Jsoup.parse(URL("https://ycccccccy.github.io/app-versioncheck/"), 30000)
        val versionElement = doc.getElementById("FKETS-android-version")
        val version = versionElement?.text() ?: "未找到版本信息"

        withContext(Dispatchers.Main) {
            // 检查版本信息
            val localVersion = "1.0.4"
            if (localVersion != version) {
                // 提示用户更新
                textView.text = "已有更新版本,请更新!!!"
            }
            if (localVersion == version) {
                // 提示用户更新
                textView.text = "已是最新版本"
            }
        }
    } catch (e: Exception) {
        // 处理异常
    }
}

触发获取答案的按键

按键A的任务也就是解析指定json文件的内容,解析方式同Windows端,这里不再赘述

Kotlin

val buttonA = findViewById<Button>(R.id.buttonA)
        buttonA.setOnClickListener {
            if (directoryUri != null) {
                val stringBuilder = StringBuilder()
                stringBuilder.append("开始尝试获取:\n")

                val directory = DocumentFile.fromTreeUri(this, directoryUri!!)
                val files = directory?.listFiles()
                val folders = files?.filter { it.isDirectory }?.sortedByDescending { it.lastModified() }?.take(3)

                fun removeHtmlTags(text: String): String {
                    return text.replace(Regex("<.*?>"), "")
                }

                if (directoryUri != null) {
                    val stringBuilder = StringBuilder()
                    stringBuilder.append("开始尝试获取:\n")

                    val directory = DocumentFile.fromTreeUri(this, directoryUri!!)
                    val files = directory?.listFiles()
                    val folders = files?.filter { it.isDirectory }?.sortedByDescending { it.lastModified() }?.take(3)

                    if (folders != null) {
                        for (folder in folders) {
                            val sdf = SimpleDateFormat("yyyy.MM.dd HH:mm:ss", Locale.getDefault())
                            val creationTime = Date(folder.lastModified())
                            stringBuilder.append("此小题的下载时间: ${sdf.format(creationTime)}\n")

                            val file = folder.findFile("content.json")
                            if (file != null) {
                                try {
                                    val inputStream = contentResolver.openInputStream(file.uri)
                                    val data = JSONObject(inputStream?.bufferedReader().use { it?.readText() })
                                    val switch: Switch = findViewById(R.id.switch_single_answer_mode)
                                    val isSwitchChecked: Boolean = switch.isChecked

                                    if (data.getJSONObject("info").has("question")) {
                                        val questions = data.getJSONObject("info").getJSONArray("question")
                                        for (j in 0 until Math.min(questions.length(), 8)) {
                                            val question = questions.getJSONObject(j)
                                            stringBuilder.append("角色扮演 ${j + 1} :\n")
                                            val answers = question.getJSONArray("std")
                                            for (k in 0 until answers.length()) {
                                                val answer = answers.getJSONObject(k)
                                                val answerText = answer.getString("value")
                                                val plainText = removeHtmlTags(answerText)
                                                stringBuilder.append("${k + 1}. $plainText\n")
                                                if (isSwitchChecked) break
                                            }
                                        }
                                    }

                                    if (data.getJSONObject("info").has("std")) {
                                        stringBuilder.append("故事复述:\n")
                                        val answers = data.getJSONObject("info").getJSONArray("std")
                                        for (k in 0 until answers.length()) {
                                            val answer = answers.getJSONObject(k)
                                            val answerText = answer.getString("value")
                                            val plainText = removeHtmlTags(answerText)
                                            stringBuilder.append("${k + 1}. $plainText\n")
                                            if (isSwitchChecked) break
                                        }
                                    }

                                    stringBuilder.append("\n")
                                } catch (e: Exception) {
                                    stringBuilder.append("错误: $e\n")
                                }
                            }
                        }
                    }
                }
                title.text = ""
                textView.text = stringBuilder.toString()

            }
        }

清空文本区域的按键

没啥好说的,也写出来吧

Kotlin

        val buttonB = findViewById<Button>(R.id.buttonB)
        buttonB.setOnClickListener {
            textView.text = ""
        }

保存访问权限

这里非常重要,不然我们就没法保存到这个来之不易的权限😭

Kotlin
    override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
            super.onActivityResult(requestCode, resultCode, resultData)

            if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
                directoryUri = resultData?.data

                // 请求一个持久的URI权限授予
                val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                contentResolver.takePersistableUriPermission(directoryUri!!, takeFlags)

                // 保存URI
                val sharedPreferences = getSharedPreferences("app_prefs", MODE_PRIVATE)
                val editor = sharedPreferences.edit()
                editor.putString("directory_uri", directoryUri.toString())
                editor.apply()
            }
        }

完整代码:

Kotlin
package com.yc.at_ets

import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.app.Activity
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.Settings
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import org.json.JSONObject
import java.io.File
import android.widget.Switch
import android.content.res.Configuration
import android.media.MediaPlayer
import android.view.View
import android.widget.Toast
import kotlinx.coroutines.*
import android.graphics.Color
import android.content.Context
import android.content.SharedPreferences
import java.util.Date
import java.text.SimpleDateFormat
import java.util.Locale
import android.os.AsyncTask
import org.jsoup.Jsoup
import java.net.URL

class FirstRunCheck(context: Context) {
    private val sharedPreferences: SharedPreferences = context.getSharedPreferences("MyApp", Context.MODE_PRIVATE)

    fun isFirstRun(): Boolean {
        return sharedPreferences.getBoolean("isFirstRun", true)
    }

    fun setFirstRun() {
        sharedPreferences.edit().putBoolean("isFirstRun", false).apply()
    }

    fun isSingleAnswerMode(): Boolean {
        return sharedPreferences.getBoolean("isSingleAnswerMode", false)
    }

    fun setSingleAnswerMode(isSingleAnswerMode: Boolean) {
        sharedPreferences.edit().putBoolean("isSingleAnswerMode", isSingleAnswerMode).apply()
    }
}

class MainActivity : AppCompatActivity() {
    private val REQUEST_CODE = 0
    private lateinit var job: Job
    var directoryUri =
        Uri.parse("content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata%2Fcom.ets100.secondary%2Ffiles%2FDownload%2FETS_SECONDARY%2Fresource")



    // 判断颜色是否为亮色
    private fun isLightColor(color: Int): Boolean {
        val red = Color.red(color)
        val green = Color.green(color)
        val blue = Color.blue(color)
        val brightness = (red * 299 + green * 587 + blue * 114) / 1000
        return brightness >= 128
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)



        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // 检查是否开启了深色模式
            val nightModeFlags = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
            if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
                // 如果开启了深色模式,设置状态栏颜色为黑色,字体颜色为白色
                window.statusBarColor = Color.BLACK
                window.decorView.systemUiVisibility = 0
            } else {
                // 如果没有开启深色模式,设置状态栏颜色为白色,字体颜色为黑色
                window.statusBarColor = Color.WHITE
                window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
            }
        }


        val firstRunCheck = FirstRunCheck(this)
        if (firstRunCheck.isFirstRun()) {
            // 这是首次运行,显示消息
            Toast.makeText(this, "欢迎使用,请点击下方授权!", Toast.LENGTH_LONG).show()

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                if (!Environment.isExternalStorageManager()) {
                    val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
                    val uri = Uri.fromParts("package", packageName, null)
                    intent.data = uri
                    startActivity(intent)
                } else {
                    // 已经获得权限,可以执行访问数据目录和所有文件的操作
                }
            } else {
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
                } else {
                    // 已经获得权限,可以执行访问数据目录和所有文件的操作
                }
            }

            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
            intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, directoryUri)
            startActivityForResult(intent, REQUEST_CODE)

            // 设置为非首次运行
            firstRunCheck.setFirstRun()
        }

        // 读取URI
        val sharedPreferences = getSharedPreferences("app_prefs", MODE_PRIVATE)
        val uriString = sharedPreferences.getString("directory_uri", null)
        if (uriString != null) {
            directoryUri = Uri.parse(uriString)
        }

        val textView = findViewById<TextView>(R.id.textView)
        val title = findViewById<TextView>(R.id.title)

        // 启动协程来获取版本信息
        GlobalScope.launch(Dispatchers.IO) {
            try {
                title.text = "正在检查更新"
                val doc = Jsoup.parse(URL("https://ycccccccy.github.io/app-versioncheck/"), 30000)
                val versionElement = doc.getElementById("FKETS-android-version")
                val version = versionElement?.text() ?: "未找到版本信息"

                withContext(Dispatchers.Main) {
                    // 检查版本信息
                    val localVersion = "1.0.4"
                    if (localVersion != version) {
                        // 提示用户更新
                        textView.text = "已有更新版本,请更新!!!"
                    }
                    if (localVersion == version) {
                        // 提示用户更新
                        textView.text = "已是最新版本"
                    }
                }
            } catch (e: Exception) {
                // 处理异常
            }
        }

        val buttonA = findViewById<Button>(R.id.buttonA)
        buttonA.setOnClickListener {
            if (directoryUri != null) {
                val stringBuilder = StringBuilder()
                stringBuilder.append("开始尝试获取:\n")

                val directory = DocumentFile.fromTreeUri(this, directoryUri!!)
                val files = directory?.listFiles()
                val folders = files?.filter { it.isDirectory }?.sortedByDescending { it.lastModified() }?.take(3)

                fun removeHtmlTags(text: String): String {
                    return text.replace(Regex("<.*?>"), "")
                }

                if (directoryUri != null) {
                    val stringBuilder = StringBuilder()
                    stringBuilder.append("开始尝试获取:\n")

                    val directory = DocumentFile.fromTreeUri(this, directoryUri!!)
                    val files = directory?.listFiles()
                    val folders = files?.filter { it.isDirectory }?.sortedByDescending { it.lastModified() }?.take(3)

                    if (folders != null) {
                        for (folder in folders) {
                            val sdf = SimpleDateFormat("yyyy.MM.dd HH:mm:ss", Locale.getDefault())
                            val creationTime = Date(folder.lastModified())
                            stringBuilder.append("此小题的下载时间: ${sdf.format(creationTime)}\n")

                            val file = folder.findFile("content.json")
                            if (file != null) {
                                try {
                                    val inputStream = contentResolver.openInputStream(file.uri)
                                    val data = JSONObject(inputStream?.bufferedReader().use { it?.readText() })
                                    val switch: Switch = findViewById(R.id.switch_single_answer_mode)
                                    val isSwitchChecked: Boolean = switch.isChecked

                                    if (data.getJSONObject("info").has("question")) {
                                        val questions = data.getJSONObject("info").getJSONArray("question")
                                        for (j in 0 until Math.min(questions.length(), 8)) {
                                            val question = questions.getJSONObject(j)
                                            stringBuilder.append("角色扮演 ${j + 1} :\n")
                                            val answers = question.getJSONArray("std")
                                            for (k in 0 until answers.length()) {
                                                val answer = answers.getJSONObject(k)
                                                val answerText = answer.getString("value")
                                                val plainText = removeHtmlTags(answerText)
                                                stringBuilder.append("${k + 1}. $plainText\n")
                                                if (isSwitchChecked) break
                                            }
                                        }
                                    }

                                    if (data.getJSONObject("info").has("std")) {
                                        stringBuilder.append("故事复述:\n")
                                        val answers = data.getJSONObject("info").getJSONArray("std")
                                        for (k in 0 until answers.length()) {
                                            val answer = answers.getJSONObject(k)
                                            val answerText = answer.getString("value")
                                            val plainText = removeHtmlTags(answerText)
                                            stringBuilder.append("${k + 1}. $plainText\n")
                                            if (isSwitchChecked) break
                                        }
                                    }

                                    stringBuilder.append("\n")
                                } catch (e: Exception) {
                                    stringBuilder.append("错误: $e\n")
                                }
                            }
                        }
                    }
                }
                title.text = ""
                textView.text = stringBuilder.toString()

            }
        }

        val buttonB = findViewById<Button>(R.id.buttonB)
        buttonB.setOnClickListener {
            textView.text = ""
        }
    }




    override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
        super.onActivityResult(requestCode, resultCode, resultData)

        if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
            directoryUri = resultData?.data

            // 请求一个持久的URI权限授予
            val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            contentResolver.takePersistableUriPermission(directoryUri!!, takeFlags)

            // 保存URI
            val sharedPreferences = getSharedPreferences("app_prefs", MODE_PRIVATE)
            val editor = sharedPreferences.edit()
            editor.putString("directory_uri", directoryUri.toString())
            editor.apply()
        }
    }
}

尾声

这是一个维护了长达0.5坤年的小工具,如果将来E听说对答案进行了混淆的话,希望能后继有人将这个工具继续维护下去,让大家都能自由获取答案😆

我们,后会有期!

上次更新于: